<?php
App::import('Config', 'SagePay.sage_pay_settings');
class PaymentController extends AppController {

	var $name = 'Payment';
	var $uses = array('User','Customer','Store.StoreOrder','Store.StoreOrderDetail','Store.StoreDelivery');
	var $permissions = array(
		'confirm' => array('admin','manag','users','copyr','firms','emplo')
	);
	var $SagePaySettings;
	
	public function beforeFilter(){
		parent::beforeFilter();
		
		$this->SagePaySettings =& new SagePaySettings();
//   		$this->options = array_merge($FileUploadSettings->defaults, $this->options);

		App::import('Helper', 'Store.Store');
		$this->Store = new StoreHelper();
	}
	
	function confirm(){
		//** Gather customer details from the session **
//		$buer = $this->User->read(null,$this->Session->read('Auth.User.id'));

		$buer = $this->Session->read("User");
			
		$strCustomerEMail      = $buer['User']["email"];
		$strBillingFirstnames  = $buer['Customer'][0]["name"];
		$strBillingSurname     = $buer['Customer'][0]["surname"];
		$strBillingAddress1    = $buer['Customer'][0]["address1"];
		$strBillingAddress2    = $buer['Customer'][0]["address2"];
		$strBillingCity        = $buer['Customer'][0]["address3"];
		$strBillingPostCode    = $buer['Customer'][0]["address4"];
		$strBillingCountry     = $buer['Customer'][0]["address5"];
		$strBillingState       = ''; //$buer['Customer'][0]["strBillingState"];
		$strBillingPhone       = $buer['Customer'][0]["phone1"];
		$bIsDeliverySame       = $buer['Customer'][0]["delivery_as_billing"]; 
		$strDeliveryFirstnames = $buer['Customer'][0]["delivery_name"];
		$strDeliverySurname    = $buer['Customer'][0]["delivery_surname"];
		$strDeliveryAddress1   = $buer['Customer'][0]["delivery_address1"];
		$strDeliveryAddress2   = $buer['Customer'][0]["delivery_address2"];
		$strDeliveryCity       = $buer['Customer'][0]["delivery_address3"];
		$strDeliveryPostCode   = $buer['Customer'][0]["delivery_address4"];
		$strDeliveryCountry    = $buer['Customer'][0]["delivery_address5"];
		$strDeliveryState      = ''; //$buer['Customer'][0]["strDeliveryState"];
		$strDeliveryPhone      = $buer['Customer'][0]["phone2"];
		
		/** Okay, build the crypt field for Form using the information in our session **
		*** First we need to generate a unique VendorTxCode for this transaction **
		*** We're using VendorName, time stamp and a random element.  You can use different methods if you wish **
		*** but the VendorTxCode MUST be unique for each transaction you send to Server **/
		$strTimeStamp = date("ymdHis", time());
		$intRandNum = rand(0,32000)*rand(0,32000);
		$strVendorTxCode = $this->SagePaySettings->strVendorName . "-" . $strTimeStamp . "-" . $intRandNum;
			
		/** Now to calculate the transaction total based on basket contents.  For security **
		*** we recalculate it here rather than relying on totals stored in the session or hidden fields **
		*** We'll also create the basket contents to pass to Form. See the Form Protocol for **
		*** the full valid basket format.  The code below converts from our "x of y" style into **
		*** the system basket format (using a 17.5% VAT calculation for the tax columns) **/
		
		$sngTotal = 0.0;
		$strThisEntry = $this->Session->read('Store.Cart.Items');

		$strBasket="";
		$iBasketItems= 0 ;
		
		
		$sum = 0.00;
		$ilosc_typow = 0;
		$total_amount = 0.00;
		$delivery_costs = 0.00;		
		if( isset($strThisEntry) && is_array($strThisEntry) && !empty($strThisEntry)){
			foreach ($strThisEntry as $item){
				$ilosc_typow =  $item['StoreProduct']['amount'];
				$total_amount += $ilosc_typow;
			
				if($item['StoreProduct']['price_przecena'] > 0 && $item['StoreProduct']['price_promocja'] == 0){
					$price = str_replace(',', '.', $item['StoreProduct']['price_przecena']);	
					$price_detal = str_replace(',', '.', $item['StoreProduct']['price_detaliczna']);								
				}else if( $item['StoreProduct']['price_promocja'] > 0){
					$price = str_replace(',', '.', $item['StoreProduct']['price_promocja']);	
					$price_detal = str_replace(',', '.', $item['StoreProduct']['price_detaliczna']);								
				}else{
					$price = str_replace(',', '.', $item['StoreProduct']['price_detaliczna']);									
				}
				$sum += $ilosc_typow *   str_replace(',', '.',$price);
				
				// Extract the Quantity and Product from the list of "x of y," entries in the cart
				$iQuantity= $ilosc_typow;
				$iProductId=$item['StoreProduct']['id'];
				// Add another item to our Form basket
				$iBasketItems=$iBasketItems+1;

				
				$strBasket=$strBasket . ":" . $item['StoreProduct']['name'] . ":" . $iQuantity;
				$strBasket=$strBasket . ":" . number_format($price / 1.2,2); /** Price ex-Vat **/
				$strBasket=$strBasket . ":" . number_format($price / 6,2); /** VAT component **/
				$strBasket=$strBasket . ":" . number_format($price,2); /** Item price **/
				$strBasket=$strBasket . ":" . number_format($price * $iQuantity,2); /** Line total **/
				
			} 
		}
		// delivery
		$StoreDelivery = $this->StoreDelivery->find('first');
		switch ($sum){
			case ($sum < $StoreDelivery['StoreDelivery']['max_1']):
				$delivery_price = str_replace(',', '.',$StoreDelivery['StoreDelivery']['price_1']);
				break;
			case ($sum >= $StoreDelivery['StoreDelivery']['max_1']):
				$delivery_price = 0;
				break;
		}
		$delivery = array();
		if($sum < $StoreDelivery['StoreDelivery']['max_1'] ){
			$delivery['StoreDelivery'][$StoreDelivery['StoreDelivery']['id']]['name'] = $StoreDelivery['StoreDelivery']['name'].' - '.__d('store','Sorry your order did not qualify for free delivery',true);		
			$delivery['StoreDelivery'][$StoreDelivery['StoreDelivery']['id']]['price'] = $delivery_price;
			$delivery['StoreDelivery'][$StoreDelivery['StoreDelivery']['id']]['currency_id'] = $StoreDelivery['StoreDelivery']['currency_id'];
			$delivery['StoreDelivery'][$StoreDelivery['StoreDelivery']['id']]['exchange'] = $StoreDelivery['StoreCurrency']['exchange'];
			
			$this->Session->write('Store.Cart.Delivery',$delivery['StoreDelivery'][$StoreDelivery['StoreDelivery']['id']]);
			$delivery = $this->Session->read('Store.Cart.Delivery');
			$delivery_costs = 0.0;
			$delivery_costs = $delivery['price'];
			$sum += str_replace(',', '.',$delivery['price']);
			
			// We've been right through the cart, so add delivery to the total and the basket
			$sngTotal=$sum;
			$strBasket=$iBasketItems+1 . $strBasket . ":{$delivery['name']}:1:{$delivery_costs}:---:{$delivery_costs}:{$delivery_costs}";
			
		}else{
			$delivery['StoreDelivery'][$StoreDelivery['StoreDelivery']['id']]['name'] = __d('store','Your order qualifies for free delivery',true);		
			$delivery['StoreDelivery'][$StoreDelivery['StoreDelivery']['id']]['price'] = 0.0;
			$delivery['StoreDelivery'][$StoreDelivery['StoreDelivery']['id']]['currency_id'] = $StoreDelivery['StoreDelivery']['currency_id'];
			$delivery['StoreDelivery'][$StoreDelivery['StoreDelivery']['id']]['exchange'] = $StoreDelivery['StoreCurrency']['exchange'];
			
			$this->Session->write('Store.Cart.Delivery',$delivery['StoreDelivery'][$StoreDelivery['StoreDelivery']['id']]);
			$delivery_costs = 0.0;
			$sngTotal=$sum;
			$strBasket=$iBasketItems+1 . $strBasket . ":Your order qualifies for free delivery:0:{$delivery_costs}:---:{$delivery_costs}:{$delivery_costs}";
		}
									

		// Now to build the Form crypt field.  For more details see the Form Protocol 2.23 
		$strPost = "VendorTxCode=" . $strVendorTxCode; /** As generated above **/
		
		// Optional: If you are a Sage Pay Partner and wish to flag the transactions with your unique partner id, it should be passed here
		if (strlen($this->SagePaySettings->strPartnerID) > 0)
		    $strPost=$strPost . "&ReferrerID=" . $this->SagePaySettings->strPartnerID;
		
		$strPost=$strPost . "&Amount=" . number_format($sngTotal,2); // Formatted to 2 decimal places with leading digit
		$strPost=$strPost . "&Currency=" . $this->SagePaySettings->strCurrency;
		// Up to 100 chars of free format description
		$strPost=$strPost . "&Description=Transaction from " . $_SERVER['HTTP_HOST'];
		
		/* The SuccessURL is the page to which Form returns the customer if the transaction is successful 
		** You can change this for each transaction, perhaps passing a session ID or state flag if you wish */
		$strPost=$strPost . "&SuccessURL=" . $this->SagePaySettings->orderSuccessful;
		
		/* The FailureURL is the page to which Form returns the customer if the transaction is unsuccessful
		** You can change this for each transaction, perhaps passing a session ID or state flag if you wish */
		$strPost=$strPost . "&FailureURL=" .  $this->SagePaySettings->orderFailed;
		
		// This is an Optional setting. Here we are just using the Billing names given.
		$strPost=$strPost . "&CustomerName=" . $strBillingFirstnames . " " . $strBillingSurname;
		
		/* Email settings:
		** Flag 'SendEMail' is an Optional setting. 
		** 0 = Do not send either customer or vendor e-mails, 
		** 1 = Send customer and vendor e-mails if address(es) are provided(DEFAULT). 
		** 2 = Send Vendor Email but not Customer Email. If you do not supply this field, 1 is assumed and e-mails are sent if addresses are provided. **/
		if ($this->SagePaySettings->bSendEMail == 0)
		    $strPost=$strPost . "&SendEMail=0";
		else {
		    
		    if ($this->SagePaySettings->bSendEMail == 1) {
		    	$strPost=$strPost . "&SendEMail=1";
		    } else {
		    	$strPost=$strPost . "&SendEMail=2";
		    }
		    
		    if (strlen($strCustomerEMail) > 0)
		        $strPost=$strPost . "&CustomerEMail=" . $strCustomerEMail;  // This is an Optional setting
		    
		    if (($this->SagePaySettings->strVendorEMail <> "[your e-mail address]") && ($this->SagePaySettings->strVendorEMail <> ""))
			    $strPost=$strPost . "&VendorEMail=" . $this->SagePaySettings->strVendorEMail;  // This is an Optional setting
		
		    // You can specify any custom message to send to your customers in their confirmation e-mail here
		    // The field can contain HTML if you wish, and be different for each order.  This field is optional
		    $strPost=$strPost . "&eMailMessage=Thank you so very much for your order.";
		}
		
		// Billing Details:
		$strPost=$strPost . "&BillingFirstnames=" . $strBillingFirstnames;
		$strPost=$strPost . "&BillingSurname=" . $strBillingSurname;
		$strPost=$strPost . "&BillingAddress1=" . $strBillingAddress1;
		if (strlen($strBillingAddress2) > 0) $strPost=$strPost . "&BillingAddress2=" . $strBillingAddress2;
		$strPost=$strPost . "&BillingCity=" . $strBillingCity;
		$strPost=$strPost . "&BillingPostCode=" . $strBillingPostCode;
		$strPost=$strPost . "&BillingCountry=" . $strBillingCountry;
		if (strlen($strBillingState) > 0) $strPost=$strPost . "&BillingState=" . $strBillingState;
		if (strlen($strBillingPhone) > 0) $strPost=$strPost . "&BillingPhone=" . $strBillingPhone;
		
		// Delivery Details:
		$strPost=$strPost . "&DeliveryFirstnames=" . $strDeliveryFirstnames;
		$strPost=$strPost . "&DeliverySurname=" . $strDeliverySurname;
		$strPost=$strPost . "&DeliveryAddress1=" . $strDeliveryAddress1;
		if (strlen($strDeliveryAddress2) > 0) $strPost=$strPost . "&DeliveryAddress2=" . $strDeliveryAddress2;
		$strPost=$strPost . "&DeliveryCity=" . $strDeliveryCity;
		$strPost=$strPost . "&DeliveryPostCode=" . $strDeliveryPostCode;
		$strPost=$strPost . "&DeliveryCountry=" . $strDeliveryCountry;
		if (strlen($strDeliveryState) > 0) $strPost=$strPost . "&DeliveryState=" . $strDeliveryState;
		if (strlen($strDeliveryPhone) > 0) $strPost=$strPost . "&DeliveryPhone=" . $strDeliveryPhone;
		
		
		$strPost=$strPost . "&Basket=" . $strBasket; // As created above 
		
		// For charities registered for Gift Aid, set to 1 to display the Gift Aid check box on the payment pages
		$strPost=$strPost . "&AllowGiftAid=0";
			
		/* Allow fine control over AVS/CV2 checks and rules by changing this value. 0 is Default 
		** It can be changed dynamically, per transaction, if you wish.  See the Server Protocol document */
		if ($this->SagePaySettings->strTransactionType!=="AUTHENTICATE")
			$strPost=$strPost . "&ApplyAVSCV2=0";
			
		/* Allow fine control over 3D-Secure checks and rules by changing this value. 0 is Default 
		** It can be changed dynamically, per transaction, if you wish.  See the Form Protocol document */
		$strPost=$strPost . "&Apply3DSecure=0";
		
		// Encrypt the plaintext string for inclusion in the hidden field
		$strCrypt = $this->encryptAndEncode($strPost);

		$strPurchaseURL = $this->SagePaySettings->strPurchaseURL;
		$strProtocol = $this->SagePaySettings->strProtocol;
		$strTransactionType = $this->SagePaySettings->strTransactionType;
		$strVendorName = $this->SagePaySettings->strVendorName;
		$products = $this->Session->read('Store.Cart.Items');
		
		
		$this->set(compact('buer','products','delivery','strCrypt','strPurchaseURL','strProtocol','strTransactionType','strVendorName','strVendorTxCode'));
	}
	
	
	/* Base 64 Encoding function **
	** PHP does it natively but just for consistency and ease of maintenance, let's declare our own function **/
	function base64Encode($plain) {
	  // Initialise output variable
	  $output = "";
	  
	  // Do encoding
	  $output = base64_encode($plain);
	  
	  // Return the result
	  return $output;
	}

	/* Base 64 decoding function **
	** PHP does it natively but just for consistency and ease of maintenance, let's declare our own function **/
	function base64Decode($scrambled) {
	  // Initialise output variable
	  $output = "";
	  
	  // Fix plus to space conversion issue
	  $scrambled = str_replace(" ","+",$scrambled);
	  
	  // Do encoding
	  $output = base64_decode($scrambled);
	  
	  // Return the result
	  return $output;
	}
	
	
	/*  The SimpleXor encryption algorithm                                                                                **
	**  NOTE: This is a placeholder really.  Future releases of Form will use AES or TwoFish.  Proper encryption      **
	**  This simple function and the Base64 will deter script kiddies and prevent the "View Source" type tampering        **
	**  It won't stop a half decent hacker though, but the most they could do is change the amount field to something     **
	**  else, so provided the vendor checks the reports and compares amounts, there is no harm done.  It's still          **
	**  more secure than the other PSPs who don't both encrypting their forms at all                                      */
	
	function simpleXor($InString, $Key) {
	  // Initialise key array
	  $KeyList = array();
	  // Initialise out variable
	  $output = "";
	  
	  // Convert $Key into array of ASCII values
	  for($i = 0; $i < strlen($Key); $i++){
	    $KeyList[$i] = ord(substr($Key, $i, 1));
	  }
	
	  // Step through string a character at a time
	  for($i = 0; $i < strlen($InString); $i++) {
	    // Get ASCII code from string, get ASCII code from key (loop through with MOD), XOR the two, get the character from the result
	    // % is MOD (modulus), ^ is XOR
	    $output.= chr(ord(substr($InString, $i, 1)) ^ ($KeyList[$i % strlen($Key)]));
	  }
	
	  // Return the result
	  return $output;
	}
	
	
	//** Wrapper function do encrypt an encode based on strEncryptionType setting **
	function encryptAndEncode($strIn) {

		
		if ($this->SagePaySettings->strEncryptionType=="XOR") 
		{
			//** XOR encryption with Base64 encoding **
			return $this->base64Encode($this->simpleXor($strIn,$this->SagePaySettings->strEncryptionPassword));
		} 
		else 
		{
			//** AES encryption, CBC blocking with PKCS5 padding then HEX encoding - DEFAULT **
	
			//** use initialization vector (IV) set from $strEncryptionPassword
	    	$strIV = $this->SagePaySettings->strEncryptionPassword;
				    	
	    	//** add PKCS5 padding to the text to be encypted
	    	$strIn = $this->addPKCS5Padding($strIn);

	    	//** perform encryption with PHP's MCRYPT module
	
			$strCrypt = @mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->SagePaySettings->strEncryptionPassword, $strIn, MCRYPT_MODE_CBC, $strIV);
			
			//** perform hex encoding and return
			return "@" . bin2hex($strCrypt);
		}
	}
	
	
	//** Wrapper function do decode then decrypt based on header of the encrypted field **
	function decodeAndDecrypt($strIn) {

		
		if (substr($strIn,0,1)=="@") 
		{
			//** HEX decoding then AES decryption, CBC blocking with PKCS5 padding - DEFAULT **
			
			//** use initialization vector (IV) set from $strEncryptionPassword
	    	$strIV = $this->SagePaySettings->strEncryptionPassword;

	    	//** remove the first char which is @ to flag this is AES encrypted
	    	$strIn = substr($strIn,1);

	    	//** HEX decoding
	    	$strIn = pack('H*', $strIn);
	    	
	    	//** perform decryption with PHP's MCRYPT module
			return @mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $this->SagePaySettings->strEncryptionPassword, $strIn, MCRYPT_MODE_CBC, $strIV); 
		} 
		else 
		{
			//** Base 64 decoding plus XOR decryption **
			return $this->simpleXor($this->base64Decode($strIn),$this->SagePaySettings->strEncryptionPassword);
		}
	}
	
	
	//** PHP's mcrypt does not have built in PKCS5 Padding, so we use this
	function addPKCS5Padding($input)
	{
	   $blocksize = 16;
	   $padding = "";
	
	   // Pad input to an even block size boundary
	   $padlength = $blocksize - (strlen($input) % $blocksize);
	   for($i = 1; $i <= $padlength; $i++) {
	      $padding .= chr($padlength);
	   }
	   
	   return $input . $padding;
	}
	
	function getToken($thisString) {
		// List the possible tokens
		$Tokens = array(
			"Status",
			"StatusDetail",
			"VendorTxCode",
			"VPSTxId",
			"TxAuthNo",
			"Amount",
			"AVSCV2", 
			"AddressResult", 
			"PostCodeResult", 
			"CV2Result", 
			"GiftAid", 
			"3DSecureStatus", 
			"CAVV",
			"AddressStatus",
			"CardType",
			"Last4Digits",
			"PayerStatus");

		// Initialise arrays
		$output = array();
		$resultArray = array();
  
		// Get the next token in the sequence
		for ($i = count($Tokens)-1; $i >= 0 ; $i--){
			// Find the position in the string
			$start = strpos($thisString, $Tokens[$i]);
			// If it's present
			if ($start !== false){
				// Record position and token name
				$resultArray[$i]->start = $start;
				$resultArray[$i]->token = $Tokens[$i];
			}
		}
  
		// Sort in order of position
		sort($resultArray);
		// Go through the result array, getting the token values
		for ($i = 0; $i<count($resultArray); $i++){
			// Get the start point of the value
			$valueStart = $resultArray[$i]->start + strlen($resultArray[$i]->token) + 1;
			// Get the length of the value
			if ($i==(count($resultArray)-1)) {
				$output[$resultArray[$i]->token] = substr($thisString, $valueStart);
			} else {
				$valueLength = $resultArray[$i+1]->start - $resultArray[$i]->start - strlen($resultArray[$i]->token) - 2;
				$output[$resultArray[$i]->token] = substr($thisString, $valueStart, $valueLength);
			}      

		}
		// Return the ouput array
		return $output;
	}
	
	
	function order_successful(){
		$strCrypt= $this->params['url']['crypt'];
		
		// Now decode the Crypt field and extract the results
		$strDecoded = $this->decodeAndDecrypt($strCrypt);
		
		$values = $this->getToken($strDecoded);
		$this->set('strDecoded',$values);
		$order = $this->StoreOrder->findByUniqidPayment($values['VendorTxCode']);
		$order_u['StoreOrder']['id'] = $order['StoreOrder']['id'];
		$order_u['StoreOrder']['status'] = $values['Status'];
		$order_u['StoreOrder']['status_detail'] = $values['StatusDetail'];
		$this->StoreOrder->id = $order['StoreOrder']['id'];
		if($this->StoreOrder->save($order_u)){
			$this->Session->setFlash(__d('store','The order have been saved.', true));
			
		}else{
			$this->log(print_r($values,true),'error_order_save_successful');
			$this->Session->setFlash(__d('store','The order have not saved. Please contact with us.', true));
		}
		$this->redirect('/');
	}
	
	
	function order_failed(){
		$strCrypt= $this->params['url']['crypt'];
		
		// Now decode the Crypt field and extract the results
		$strDecoded = $this->decodeAndDecrypt($strCrypt);
		$values = $this->getToken($strDecoded);
		$order = $this->StoreOrder->findByUniqidPayment($values['VendorTxCode']);
		
		$strStatus=$values['Status'];
		// Determine the reason this transaction was unsuccessful
		
		if ($strStatus=="NOTAUTHED")
			$values['StatusDetail'] = "You payment was declined by the bank.  This could be due to insufficient funds, or incorrect card details.";
		else if ($strStatus=="ABORT")
			$values['StatusDetail']="You have chosen to cancel your order. We are sorry to lose your custom and will be happy to answer any questions or concerns you may have. If you wish to change or resubmit your order you can do so here.";
		else if ($strStatus=="REJECTED") 
			$values['StatusDetail']="Your order did not meet our minimum fraud screening requirements. If you have questions about our fraud screening rules, or wish to contact us to discuss this, please call.";
		else if ($strStatus=="INVALID" or $strStatus=="MALFORMED")
			$values['StatusDetail']="We could not process your order because we have been unable to register your transaction with our Payment Gateway. You can place the order over the telephone instead by calling to us.";
		else if ($strStatus=="ERROR")
			$values['StatusDetail']="We could not process your order because our Payment Gateway service was experiencing difficulties. You can place the order over the telephone instead by calling to us.";
		else
			$values['StatusDetail']="The transaction process failed. Please contact us with the date and time of your order and we will investigate.";
		
		$order['StoreOrder']['status'] = $values['Status'];
		$order['StoreOrder']['status_detail'] = $values['StatusDetail'];
		
		$this->StoreOrder->query('UPDATE '.$this->StoreOrder->useTable.' SET status  = "'.$values['Status'].'",status_detail = "'.$values['StatusDetail'].'"  WHERE id = '.$order['StoreOrder']['id'] );
		$this->Session->setFlash($values['StatusDetail']);
//		$this->log(print_r($values,true),'error_order_save_failed');
//		$this->log(print_r($order,true),'error_order_save_failed');

		$this->redirect('/');
	}
}